using System;
using System.IO;
using System.Text;

namespace org.lionsoul.ip2region
{


    /// <summary>
    /// ip db searcher class (Not thread safe)
    /// 
    /// @author chenxin<chenxin619315@gmail.com>
    /// </summary>
    public class DbSearcher
    {
        public const int BTREE_ALGORITHM = 1;
        public const int BIN_ALGORITHM = 2;

        /// <summary>
        /// db config
        /// </summary>
        private DbConfig dbConfig = null;

        /// <summary>
        /// db file access handler
        /// </summary>
        private FileStream raf = null;

        /// <summary>
        /// header blocks buffer 
        /// </summary>
        private long[] HeaderSip = null;
        private int[] HeaderPtr = null;
        private int headerLength;

        /// <summary>
        /// super blocks info 
        /// </summary>
        private long firstIndexPtr = 0;
        private long lastIndexPtr = 0;
        private int totalIndexBlocks = 0;

        /// <summary>
        /// construct class
        /// </summary>
        /// <param name="bdConfig"> </param>
        /// <param name="dbFile"> </param>
        /// <exception cref="FileNotFoundException">  </exception>
        //JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
        //ORIGINAL LINE: public DbSearcher(DbConfig dbConfig, String dbFile) throws java.io.FileNotFoundException
        public DbSearcher(DbConfig dbConfig, string dbFile)
        {
            this.dbConfig = dbConfig;
            raf = new FileStream(dbFile, FileMode.Open);
        }

        /// <summary>
        /// get by index ptr
        /// </summary>
        /// <param name="indexPtr"> </param>
        /// <exception cref="IOException">  </exception>
        //JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
        //ORIGINAL LINE: public DataBlock getByIndexPtr(long ptr) throws java.io.IOException
        public virtual DataBlock getByIndexPtr(long ptr)
        {
            raf.Seek(ptr,SeekOrigin.Begin);
            byte[] buffer = new byte[12];
            raf.Read(buffer, 0, buffer.Length);
            //long startIp = Util.getIntLong(buffer, 0);
            //long endIp = Util.getIntLong(buffer, 4);
            long extra = Util.getIntLong(buffer, 8);

            int dataLen = (int)((extra >> 24) & 0xFF);
            int dataPtr = (int)((extra & 0x00FFFFFF));

            raf.Seek(dataPtr,SeekOrigin.Begin);
            byte[] data = new byte[dataLen];
            raf.Read(data, 0, data.Length);

            int city_id = (int)Util.getIntLong(data, 0);
            string region = Encoding.UTF8.GetString(data, 4, data.Length - 4);

            return new DataBlock(city_id, region);
        }

        /// <summary>
        /// get the region with a int ip address with b-tree algorithm
        /// </summary>
        /// <param name="ip"> </param>
        /// <exception cref="IOException">  </exception>
        //JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
        //ORIGINAL LINE: public DataBlock btreeSearch(long ip) throws java.io.IOException
        public virtual DataBlock btreeSearch(long ip)
        {
            //check and load the header
            if (HeaderSip == null)
            {
                raf.Seek(8L,SeekOrigin.Begin); //pass the super block
                //byte[] b = new byte[dbConfig.getTotalHeaderSize()];
                byte[] b = new byte[4096];
                raf.Read(b, 0, b.Length);

                //fill the header
                int len = b.Length >> 3, idx = 0; //b.lenght / 8
                HeaderSip = new long[len];
                HeaderPtr = new int[len];
                long startIp, dataPtr;
                for (int i = 0; i < b.Length; i += 8)
                {
                    startIp = Util.getIntLong(b, i);
                    dataPtr = Util.getIntLong(b, i + 4);
                    if (dataPtr == 0)
                    {
                        break;
                    }

                    HeaderSip[idx] = startIp;
                    HeaderPtr[idx] = (int)dataPtr;
                    idx++;
                }

                headerLength = idx;
            }

            //1. define the index block with the binary search
            if (ip == HeaderSip[0])
            {
                return getByIndexPtr(HeaderPtr[0]);
            }
            else if (ip == HeaderSip[headerLength - 1])
            {
                return getByIndexPtr(HeaderPtr[headerLength - 1]);
            }

            int l = 0, h = headerLength, sptr = 0, eptr = 0;
            while (l <= h)
            {
                int m = (l + h) >> 1;

                //perfetc matched, just return it
                if (ip == HeaderSip[m])
                {
                    if (m > 0)
                    {
                        sptr = HeaderPtr[m - 1];
                        eptr = HeaderPtr[m];
                    }
                    else
                    {
                        sptr = HeaderPtr[m];
                        eptr = HeaderPtr[m + 1];
                    }

                    break;
                }

                //less then the middle value
                if (ip < HeaderSip[m])
                {
                    if (m == 0)
                    {
                        sptr = HeaderPtr[m];
                        eptr = HeaderPtr[m + 1];
                        break;
                    }
                    else if (ip > HeaderSip[m - 1])
                    {
                        sptr = HeaderPtr[m - 1];
                        eptr = HeaderPtr[m];
                        break;
                    }
                    h = m - 1;
                }
                else
                {
                    if (m == headerLength - 1)
                    {
                        sptr = HeaderPtr[m - 1];
                        eptr = HeaderPtr[m];
                        break;
                    }
                    else if (ip <= HeaderSip[m + 1])
                    {
                        sptr = HeaderPtr[m];
                        eptr = HeaderPtr[m + 1];
                        break;
                    }
                    l = m + 1;
                }
            }

            //match nothing just stop it
            if (sptr == 0)
            {
                return null;
            }

            //2. search the index blocks to define the data
            int blockLen = eptr - sptr, blen = IndexBlock.IndexBlockLength;
            byte[] iBuffer = new byte[blockLen + blen]; //include the right border block
            raf.Seek(sptr,SeekOrigin.Begin);
            raf.Read(iBuffer, 0, iBuffer.Length);

            l = 0;
            h = blockLen / blen;
            long sip, eip, dataptr = 0;
            while (l <= h)
            {
                int m = (l + h) >> 1;
                int p = m * blen;
                sip = Util.getIntLong(iBuffer, p);
                if (ip < sip)
                {
                    h = m - 1;
                }
                else
                {
                    eip = Util.getIntLong(iBuffer, p + 4);
                    if (ip > eip)
                    {
                        l = m + 1;
                    }
                    else
                    {
                        dataptr = Util.getIntLong(iBuffer, p + 8);
                        break;
                    }
                }
            }

            //not matched
            if (dataptr == 0)
            {
                return null;
            }

            //3. get the data
            int dataLen = (int)((dataptr >> 24) & 0xFF);
            int dataPtr1 = (int)((dataptr & 0x00FFFFFF));

            raf.
                Seek(dataPtr1,SeekOrigin.Begin);
            byte[] data = new byte[dataLen];
            raf.Read(data, 0, data.Length);

            int city_id = (int)Util.getIntLong(data, 0);
            string region = Encoding.UTF8.GetString(data, 4, data.Length - 4);

            return new DataBlock(city_id, region);
        }

        /// <summary>
        /// get the region throught the ip address with b-tree search algorithm
        /// </summary>
        /// <param name="ip"> </param>
        /// <returns>    DataBlock </returns>
        /// <exception cref="IOException">  </exception>
        //JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
        //ORIGINAL LINE: public DataBlock btreeSearch(String ip) throws java.io.IOException
        public virtual DataBlock btreeSearch(string ip)
        {
            return btreeSearch(Util.ip2long(ip));
        }

        /// <summary>
        /// get the region with a int ip address with binary search algorithm
        /// </summary>
        /// <param name="ip"> </param>
        /// <exception cref="IOException">  </exception>
        //JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
        //ORIGINAL LINE: public DataBlock binarySearch(long ip) throws java.io.IOException
        public virtual DataBlock binarySearch(long ip)
        {
            int blen = IndexBlock.IndexBlockLength;
            if (totalIndexBlocks == 0)
            {
                raf.Seek(0L,SeekOrigin.Begin);
                byte[] superBytes = new byte[8];
                raf.Read(superBytes, 0, superBytes.Length);
                //initialize the global vars
                firstIndexPtr = Util.getIntLong(superBytes, 0);
                lastIndexPtr = Util.getIntLong(superBytes, 4);
                totalIndexBlocks = (int)((lastIndexPtr - firstIndexPtr) / blen) + 1;
            }

            //search the index blocks to define the data
            int l = 0, h = totalIndexBlocks;
            byte[] buffer = new byte[blen];
            long sip, eip, dataptr = 0;
            while (l <= h)
            {
                int m = (l + h) >> 1;
                raf.Seek(firstIndexPtr + m * blen,SeekOrigin.Begin); //set the file pointer
                raf.Read(buffer, 0, buffer.Length);
                sip = Util.getIntLong(buffer, 0);
                if (ip < sip)
                {
                    h = m - 1;
                }
                else
                {
                    eip = Util.getIntLong(buffer, 4);
                    if (ip > eip)
                    {
                        l = m + 1;
                    }
                    else
                    {
                        dataptr = Util.getIntLong(buffer, 8);
                        break;
                    }
                }
            }

            //not matched
            if (dataptr == 0)
            {
                return null;
            }

            //get the data
            int dataLen = (int)((dataptr >> 24) & 0xFF);
            int dataPtr = (int)((dataptr & 0x00FFFFFF));

            raf.Seek(dataPtr,SeekOrigin.Begin);
            byte[] data = new byte[dataLen];
            raf.Read(data, 0, data.Length);

            int city_id = (int)Util.getIntLong(data, 0);
            string region = Encoding.UTF8.GetString(data, 4, data.Length - 4);

            return new DataBlock(city_id, region);
        }

        /// <summary>
        /// get the region throught the ip address with binary search algorithm
        /// </summary>
        /// <param name="ip"> </param>
        /// <returns>    DataBlock </returns>
        /// <exception cref="IOException">  </exception>
        //JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
        //ORIGINAL LINE: public DataBlock binarySearch(String ip) throws java.io.IOException
        public virtual DataBlock binarySearch(string ip)
        {
            return binarySearch(Util.ip2long(ip));
        }

        /// <summary>
        /// get the db config
        /// </summary>
        /// <returns>    DbConfig </returns>
        public virtual DbConfig DbConfig
        {
            get
            {
                return dbConfig;
            }
        }

        /// <summary>
        /// close the db 
        /// </summary>
        /// <exception cref="IOException">  </exception>
        //JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
        //ORIGINAL LINE: public void close() throws java.io.IOException
        public virtual void close()
        {
            HeaderSip = null; //let gc do its work
            HeaderPtr = null;
            raf.Close();
        }
    }
}